﻿using System.Linq;
using System.Threading;
using System.Windows.Forms;
using HighDiveEngine.Aggregates;
using HighDiveEngine.Model;
using HighDiveJudge.Interfaces;
using HighDiveJudge.Resources;

namespace HighDiveJudge
{
    public class JudgeApp : IJudgeApp
    {

        private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        #region Properties
        
        public Dive CurrentDive { get; set; }
        public DiveList DiveHistory { get; set; }
        public bool Connected { get; set; }

        private string _adminIpAdress;
        private string _idString;
        private int _listenPort;
        private Thread _wait;       
        private const int ConnectionTimeOutLimitSeconds = 30;

        #endregion


        #region Constructors

        public JudgeApp()
        {
            Log.Info("Hello");
            ClearDiveLists();
            DiveHistory = new DiveList();
            TcpHandler.EventContestEnded += _contestEnded;
            TcpHandler.EventJudgeLoginSuccessful += _loginSuccessful;
            TcpHandler.EventJudgeLoginRefused += _loginRefused;
            TcpHandler.EventConnectionFailed += _connectionFailed;
            TcpHandler.EventJudgeNewDiveReceived += NewDiveReceived;
            TcpHandler.EventJudgeNewDiveScoreReceived += NewDiveScoreReceived;
            TcpHandler.EventIsAlive += _isAlive;
        }

        private void _contestEnded()
        {
            if (EventContestEnded != null) 
                EventContestEnded();
        }

        private void _isAlive()
        {
            TcpHandler.Instance.SendIsAliveToAdmin(_adminIpAdress, _idString);
        }

        #endregion


        #region Public Methods

        /// <summary>
        /// Attempts a login to the HighDiveJudge application
        /// </summary>
        /// <param name="ipAdress">The admin machine's ip adress</param>
        /// <param name="idString">The judge's IdString</param>
        /// <param name="listenPort">The judge machine's listen port</param>
        public void Login(string idString, string ipAdress, int listenPort)
        {
            //set variables
            _adminIpAdress = ipAdress;
            _idString = idString;
            _listenPort = listenPort;

            //start new tcp handler listen thread
            string[] ip = TcpHandler.Instance.IpAddress.Split(':');
            TcpHandler.Instance.IpAddress = ip[0] + ":" + _listenPort;

            //send login request
            TcpHandler.Instance.SendLoginRequestToAdmin(_adminIpAdress, _idString, listenPort);

            //start waiting thread
            _wait = new Thread(WaitForConnection) { IsBackground = true };
            _wait.Start();
        }

        /// <summary>
        /// Logs out and notifies tries to notify the admin machine
        /// </summary>
        public void Logout()
        {
            if (Connected)
            {
                Connected = false;
                ClearDiveLists();
                TcpHandler.Instance.SendLogoutNotificationToAdmin(_adminIpAdress, _idString);
            }
                
        }

        /// <summary>
        /// Updates the score of a dive and sends it to the admin
        /// </summary>
        /// <param name="dive">The dive on which to update the score</param>
        /// <param name="score">The score</param>
        public void UpdateScore(Dive dive, double score)
        {
            if (Connected)
            {
                DiveScore ds = new DiveScore() { Score = score };
                dive.DiveScoreList.Clear();
                dive.DiveScoreList.Add(ds);
                TcpHandler.Instance.SendScoreToAdmin(_adminIpAdress, _idString, dive);
            }
            else
            {
                throw new JudgeAppException("Not logged in!");
            }
        }

        /// <summary>
        /// Updates the divehistory, if there is a currently active dive it will be added to the history instead
        /// </summary>
        public void UpdateDiveHistory()
        {
            if (DiveHistory.Count == 3)
            {
                DiveHistory.Remove(DiveHistory.Last());
            }

            DiveHistory.Insert(0, CurrentDive);
            CurrentDive = null;
        }

        /// <summary>
        /// Clears the current dive and dive history
        /// </summary>
        public void ClearDiveLists()
        {
            CurrentDive = null;
            DiveHistory = new DiveList();
        }

        /// <summary>
        /// Called when a new dive has been received from the admin application
        /// </summary>
        /// <param name="dive">The received dive</param>
        public void NewDiveReceived(Dive dive)
        {
            if (CurrentDive == null)    //CurrentDive is empty, received next dive in admin's queue
            {
                CurrentDive = dive;
                if (EventUpdateFromAdminProgram != null)
                    EventUpdateFromAdminProgram();
            }
            else if (CurrentDive.Equals(dive))  //admin edited it's CurrentDive
            {
                CurrentDive = dive;
                if (EventUpdateFromAdminProgram != null)
                    EventUpdateFromAdminProgram();
            }
            else    //somehow received an invalid dive
            {
                TcpHandler.Instance.SendDiveReceiveErrorToAdmin(_adminIpAdress, _idString);
            }
        }

        /// <summary>
        /// Called when the admin application forcefully updates the score of a judge
        /// </summary>
        /// <param name="dive">The involved dive</param>
        /// <param name="score">The updated score</param>
        public void NewDiveScoreReceived(DiveScore score, Dive dive)
        {
            //find the correct dive to update
            Dive diveToUpdate = null;
            if (dive.Equals(CurrentDive))
                diveToUpdate = CurrentDive;
            else
            {
                foreach (Dive d in DiveHistory)
                {
                    if (d.Equals(dive))
                    {
                        diveToUpdate = d;
                        break;
                    }
                }
            }

            //control check to see if updated score actually belongs to this judge
            if (diveToUpdate != null && score.Judge.IdString == _idString)
            {
                diveToUpdate.DiveScoreList.Clear();
                diveToUpdate.DiveScoreList.Add(score);

                //update divehistory if dive to update is currentDive
                if (diveToUpdate == CurrentDive)
                    UpdateDiveHistory();

                if (EventUpdateFromAdminProgram != null)
                    EventUpdateFromAdminProgram();
            }
        }
        
        #endregion


        #region Private Methods

        /// <summary>
        /// Called when the admin application reports a successful login attempt
        /// </summary>
        private void _loginSuccessful()
        {
            if (!Connected)
            {
                Connected = true;

                //terminate the wait-for-connection thread
                if (_wait != null)
                    _wait.Abort();

                if (EventLoginSuccessful != null)
                    EventLoginSuccessful();
            }
        }

        /// <summary>
        /// Called when the admin application refuses login due to bad idString
        /// </summary>
        private void _loginRefused()
        {
            if (!Connected)
            {
                //terminate the wait-for-connection thread
                if (_wait != null)
                    _wait.Abort();

                if (EventLoginRefused != null)
                    EventLoginRefused();
            }
        }

        /// <summary>
        /// Called when TcpEngine fails to connect or the connection attempt timeout is hit
        /// </summary>
        /// <param name="ip"></param>
        private void _connectionFailed(string ip)
        {
            Connected = false;
            ClearDiveLists();

            //terminate the wait-for-connection thread
            if (_wait != null)
                _wait.Abort();

            if (EventConnectionFailed != null)
                EventConnectionFailed();
        }

        /// <summary>
        /// Timeout method used to wait for a successful login report from the admin application
        /// </summary>
        private void WaitForConnection()
        {
            try
            {
                Thread.Sleep(1000 * ConnectionTimeOutLimitSeconds);
                if (!Connected && EventConnectionFailed != null)
                    EventConnectionFailed();
            }
            catch
            {
                //force termination on ThreadAbortException
            }
        }

        #endregion


        #region IJudgeApp

        public event MethodInvoker EventContestEnded;
        public event MethodInvoker EventLoginSuccessful;
        public event MethodInvoker EventLoginRefused;
        public event MethodInvoker EventConnectionFailed;
        public event MethodInvoker EventUpdateFromAdminProgram;

        #endregion
    }
}
